Skip to contentMethod: buildClone(Object, Field, Object, CloneConfiguration)
1: /*
2: * JOPA
3: * Copyright (C) 2024 Czech Technical University in Prague
4: *
5: * This library is free software; you can redistribute it and/or
6: * modify it under the terms of the GNU Lesser General Public
7: * License as published by the Free Software Foundation; either
8: * version 3.0 of the License, or (at your option) any later version.
9: *
10: * This library is distributed in the hope that it will be useful,
11: * but WITHOUT ANY WARRANTY; without even the implied warranty of
12: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13: * Lesser General Public License for more details.
14: *
15: * You should have received a copy of the GNU Lesser General Public
16: * License along with this library.
17: */
18: package cz.cvut.kbss.jopa.sessions;
19:
20: import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException;
21: import cz.cvut.kbss.jopa.proxy.change.ChangeTrackingIndirectCollection;
22: import cz.cvut.kbss.jopa.proxy.change.ChangeTrackingIndirectMap;
23: import cz.cvut.kbss.jopa.sessions.util.CloneConfiguration;
24: import cz.cvut.kbss.jopa.sessions.util.CloneRegistrationDescriptor;
25: import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils;
26:
27: import java.lang.reflect.Constructor;
28: import java.lang.reflect.Field;
29: import java.lang.reflect.InvocationTargetException;
30: import java.util.Collection;
31: import java.util.Collections;
32: import java.util.HashMap;
33: import java.util.Map;
34: import java.util.Map.Entry;
35: import java.util.Optional;
36:
37: class MapInstanceBuilder extends AbstractInstanceBuilder {
38:
39: private static final Class<?> singletonMapClass = Collections.singletonMap(null, null).getClass();
40:
41: MapInstanceBuilder(CloneBuilder builder, UnitOfWork uow) {
42: super(builder, uow);
43: }
44:
45: @Override
46: Object buildClone(Object cloneOwner, Field field, Object original, CloneConfiguration configuration) {
47: Map<?, ?> orig = (Map<?, ?>) original;
48:• if (original instanceof ChangeTrackingIndirectCollection) {
49: orig = ((ChangeTrackingIndirectCollection<Map<?, ?>>) original).unwrap();
50: }
51:• if (orig == Collections.emptyMap()) {
52: return orig;
53: }
54: final Class<?> origCls = orig.getClass();
55: Map<?, ?> clone;
56: clone = cloneUsingDefaultConstructor(cloneOwner, field, origCls, orig, configuration);
57:• if (clone == null) {
58:• if (singletonMapClass.isInstance(orig)) {
59: clone = buildSingletonClone(cloneOwner, field, orig, configuration);
60:• } else if (Collections.emptyMap().equals(orig)) {
61: clone = orig;
62: } else {
63: throw new IllegalArgumentException("Unsupported map type " + origCls);
64: }
65: }
66: clone = new ChangeTrackingIndirectMap<>(cloneOwner, field, uow, clone);
67: return clone;
68:
69: }
70:
71: private Map<?, ?> cloneUsingDefaultConstructor(Object cloneOwner, Field field, Class<?> origCls, Map<?, ?> original,
72: CloneConfiguration configuration) {
73: Optional<Map<?, ?>> result = createNewInstance(origCls, original.size());
74: result.ifPresent(r -> cloneMapContent(cloneOwner, field, original, r, configuration));
75: return result.orElse(null);
76: }
77:
78: private static Optional<Map<?, ?>> createNewInstance(Class<?> type, int size) {
79: Map<?, ?> result = null;
80: final Class<?>[] types = {int.class};
81: Object[] params;
82: Constructor<?> c = getDeclaredConstructorFor(type, types);
83: if (c != null) {
84: params = new Object[1];
85: params[0] = size;
86: } else {
87: c = getDeclaredConstructorFor(type, null);
88: params = null;
89: }
90: if (c == null) {
91: return Optional.empty();
92: }
93: try {
94: result = (Map<?, ?>) c.newInstance(params);
95: } catch (InstantiationException | IllegalArgumentException | InvocationTargetException e) {
96: throw new OWLPersistenceException(e);
97: } catch (IllegalAccessException e) {
98: logConstructorAccessException(c, e);
99: // Do nothing
100: }
101: return Optional.ofNullable(result);
102: }
103:
104: private Map<?, ?> buildSingletonClone(Object cloneOwner, Field field, Map<?, ?> orig,
105: CloneConfiguration configuration) {
106: Entry<?, ?> e = orig.entrySet().iterator().next();
107: Object key = CloneBuilder.isImmutable(e.getKey()) ? e.getKey() :
108: cloneObject(cloneOwner, field, e.getKey(), configuration);
109: Object value = CloneBuilder.isImmutable(e.getValue()) ? e.getValue() :
110: cloneObject(cloneOwner, field, e.getValue(), configuration);
111: if ((value instanceof Collection || value instanceof Map) && !(value instanceof ChangeTrackingIndirectCollection)) {
112: value = uow.createIndirectCollection(value, cloneOwner, field);
113: }
114: return Collections.singletonMap(key, value);
115: }
116:
117: private void cloneMapContent(Object cloneOwner, Field field, Map<?, ?> source,
118: Map<?, ?> target, CloneConfiguration configuration) {
119: if (source.isEmpty()) {
120: return;
121: }
122: final Map<Object, Object> m = (Map<Object, Object>) target;
123: final Entry<?, ?> tmp = source.entrySet().iterator().next();
124: // Note: If we encounter null -> null mapping first, the whole map will be treated as immutable type map, which can be incorrect
125: final boolean keyPrimitive = CloneBuilder.isImmutable(tmp.getKey());
126: final boolean valuePrimitive = CloneBuilder.isImmutable(tmp.getValue());
127: for (Entry<?, ?> e : source.entrySet()) {
128: Object key;
129: Object value;
130: if (keyPrimitive) {
131: if (valuePrimitive) {
132: m.putAll(source);
133: break;
134: }
135: key = e.getKey();
136: value = cloneObject(cloneOwner, field, e.getValue(), configuration);
137: } else {
138: key = cloneObject(cloneOwner, field, e.getKey(), configuration);
139: value = valuePrimitive ? e.getValue() : cloneObject(cloneOwner, field, e.getValue(), configuration);
140: }
141: m.put(key, value);
142: }
143: }
144:
145: private Object cloneObject(Object owner, Field field, Object obj, CloneConfiguration configuration) {
146: Object clone;
147: if (obj == null) {
148: clone = null;
149: } else if (builder.isTypeManaged(obj.getClass())) {
150: clone = uow.registerExistingObject(obj, new CloneRegistrationDescriptor(configuration.getDescriptor()).postCloneHandlers(configuration.getPostRegister()));
151: } else {
152: clone = builder.buildClone(owner, field, obj, configuration.getDescriptor());
153: }
154: return clone;
155: }
156:
157: @Override
158: void mergeChanges(Field field, Object target, Object originalValue, Object cloneValue) {
159: assert (originalValue == null) || (originalValue instanceof Map);
160: assert cloneValue instanceof Map;
161:
162: Map<Object, Object> orig = (Map<Object, Object>) originalValue;
163: final Map<Object, Object> clone = cloneValue instanceof ChangeTrackingIndirectCollection ?
164: ((ChangeTrackingIndirectCollection<Map<Object, Object>>) cloneValue).unwrap() : (Map<Object, Object>) cloneValue;
165: if (orig == null) {
166: orig = (Map<Object, Object>) createNewInstance(clone.getClass(), clone.size()).orElseGet(() -> createDefaultMap(clone.size()));
167: EntityPropertiesUtils.setFieldValue(field, target, orig);
168: }
169: orig.clear();
170: if (clone.isEmpty()) {
171: return;
172: }
173: for (Entry<?, ?> e : clone.entrySet()) {
174: final Object key = e.getKey();
175: final Object value = e.getValue();
176: final Object keyToPut = uow.contains(key) ? builder.getOriginal(key) : key;
177: final Object valueToPut = uow.contains(value) ? builder.getOriginal(value) : value;
178: orig.put(keyToPut, valueToPut);
179: }
180: }
181:
182: private static Map<Object, Object> createDefaultMap(int size) {
183: return new HashMap<>(size > 1 ? size : 16);
184: }
185:
186: @Override
187: boolean populatesAttributes() {
188: return true;
189: }
190: }